#include <stdexcept>
#include <assert.h>

#include <config.h>
#include <Tools/String.h>
#include "CellDB.h"

SizeType CellDB::CalcOffset(SizeType iFileNum) const
{
	return sizeof(SizeType)*2 + iBlockCount/8 + iBlockSize * iFileNum;
};

void CellDB::SetBit(SizeType iFileNum, bool value)
{
	const SizeType iMapOffset = iFileNum / 8;
	const SizeType iByteOffset = iFileNum % 8;

	if(value)
		ucMap[iMapOffset] = ucMap[iMapOffset] | (1<<iByteOffset);
	else
		ucMap[iMapOffset] = ucMap[iMapOffset] & ~(1<<iByteOffset);

	myFile.PWrite(ucMap + iMapOffset, 1, sizeof(SizeType)*2 + iMapOffset);
};

SizeType CellDB::FindFirstFree() const
{
	for( ; iFirstFree < iBlockCount; ++iFirstFree)
	{
		const SizeType iMapOffset = iFirstFree / 8;
		const SizeType iByteOffset = iFirstFree % 8;
		if(ucMap[iMapOffset] & (1<<iByteOffset))
			break;
	}
	return iFirstFree;
};

CellDB::CellDB(const char * filePath) : myFile(filePath, FM_OpenRW), 
	iBlockSize(MAX_CELLDB_CELL_SIZE), iBlockCount(MAX_CELLDB_CELL_COUNT), 
	ucMap(NULL), iFirstFree(0)  
{
	if(false == myFile.IsOpened())
	{
		myFile.Open(filePath, FM_OpenRW | FM_Create);
		if(false == myFile.IsOpened())
			throw std::runtime_error(String("Cannot create new CellDB %s!", filePath));

		if(false == myFile.Truncate(CalcOffset(iBlockCount)))
			throw std::runtime_error(String("Cannot allocate space for CellDB %s!", filePath));

		if(sizeof(SizeType) != myFile.PWrite(&iBlockSize, sizeof(SizeType), 0))
			throw std::runtime_error(String("Cannot write header into CellDB %s!", filePath));

		if(sizeof(SizeType) != myFile.PWrite(&iBlockCount, sizeof(SizeType), sizeof(SizeType)))
			throw std::runtime_error(String("Cannot write header into CellDB %s!", filePath));

		ucMap = new unsigned char[iBlockCount/8];
		std::fill_n(ucMap, iBlockCount/8, 0);
	}
	else
	{
		if(sizeof(SizeType) != myFile.PRead(&iBlockSize, sizeof(SizeType), 0))
			throw std::runtime_error(String("Cannot read header from CellDB %s!", filePath));

		if(sizeof(SizeType) != myFile.PRead(&iBlockCount, sizeof(SizeType), sizeof(SizeType)))
			throw std::runtime_error(String("Cannot read header from CellDB %s!", filePath));

		const SSizeType iMapSize = iBlockCount/8;
		ucMap = new unsigned char[iMapSize];
		if(iMapSize != myFile.PRead(ucMap, iMapSize, sizeof(SizeType)*2))
			throw std::runtime_error(String("Cannot read file map fron CellDB %s!", filePath));
	}

	iFirstFree = FindFirstFree();
};

CellDB::~CellDB()
{
	if(NULL != ucMap)
	{
		delete [] ucMap;
		ucMap = NULL;
	}
};

bool CellDB::IsFull() const
{
	return (iFirstFree == iBlockCount); 
};

SizeType CellDB::CreateFile(const void * data, SizeType dataLen)
{
	/* need locking here!*/
	if(IsFull())
		return INVALID_SIZE;

	const SizeType iFile = FindFirstFree();
	SetBit(iFile, true);
	
	return iFile;
};

SizeType CellDB::ReadFile(SizeType iFile, void * data, SizeType dataLen, SizeType offset) const
{
	assert(dataLen + offset < iBlockSize);
	assert(true == myFile.IsOpened());
	const SizeType iFileOffset = CalcOffset(iFile) + offset;
	return myFile.PRead(data, dataLen, iFileOffset);
};

SizeType CellDB::WriteFile(SizeType iFile, const void * data, SizeType dataLen, SizeType offset)
{
	assert(dataLen + offset < iBlockSize);
	assert(true == myFile.IsOpened());
	const SizeType iFileOffset = CalcOffset(iFile) + offset;
	return myFile.PWrite(data, dataLen, iFileOffset);
};

